package org.marlin.http.proxy;

import org.marlin.http.annotation.HttpBody;
import org.marlin.http.annotation.HttpContext;
import org.marlin.http.annotation.HttpHeader;
import org.marlin.http.annotation.HttpParam;
import org.marlin.http.handler.HttpHandler;
import org.marlin.http.http.Body;
import org.marlin.http.http.Context;
import org.marlin.http.http.Header;
import org.marlin.http.http.HttpConnectionManager;
import org.marlin.http.http.Uri;

import java.io.IOException;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.apache.http.HttpException;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.impl.client.CloseableHttpClient;


/**
 * init version 1.5.0
 * @author Jack ZG
 * @since 1.0.0
 */
public class HttpProxy<T>
        implements InvocationHandler, Serializable {
    private static final long serialVersionUID = -6424540398559729838L;
    private final Map<Method, Uri> methodCache;
    private static CloseableHttpClient HttpClient = HttpConnectionManager.getHttpClient();
    private HttpHandler httpHandler;

    public HttpProxy(Map<Method, Uri> methodCache, HttpHandler httpHandler) {
        this.methodCache = methodCache;
        this.httpHandler = httpHandler;
    }


    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object body = null;
        Body params = new Body();
        Header headers = new Header();
        Context contexts = new Context();
        Uri uri = this.methodCache.get(method);
        String url = uri.getUrl();
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
        if (args != null) {
            for (int i = 0, size = args.length; i < size; i++) {
                Annotation[] ans = parameterAnnotations[i];
                List<HttpBody> httpBodyList = new ArrayList<>(1);
                List<HttpParam> httpParamList = new ArrayList<>(1);
                List<HttpContext> httpContextList = new ArrayList<>(1);
                List<HttpHeader> httpHeaderList = new ArrayList<>(1);
                findHttpAnnotation(ans, httpBodyList, httpParamList, httpHeaderList, httpContextList);
                if (httpParamList.size() > 0) {
                    HttpParam param = httpParamList.get(0);
                    String paramKey = param.name();
                    if (hasPathParam(url, paramKey)) {
                        url = url.replace("${" + paramKey + "}", String.valueOf(args[i]));
                    } else {
                        params.addParam(paramKey, args[i]);
                    }
                } else if (httpHeaderList.size() > 0) {
                    HttpHeader httpHeader = httpHeaderList.get(0);
                    headers.addParam(httpHeader.name(), args[i]);
                } else if (httpContextList.size() > 0) {
                    HttpContext httpContext = httpContextList.get(0);
                    contexts.addContext(httpContext.name(), args[i]);
                } else {

                    body = args[i];
                }
            }
        }
        return executeHttpRequest(body, uri, url, params, headers, contexts, method.getReturnType());
    }


    private Object executeHttpRequest(Object body, Uri uri, String url, Body params, Header headers, Context contexts, Class<?> returnType) throws IOException, HttpException {
        HttpUriRequest urlRequest = this.httpHandler.makeRequest(uri, url, params, headers, contexts, body);
        CloseableHttpResponse response = HttpClient.execute(urlRequest);
        return this.httpHandler.handleResult((HttpResponse) response, uri, returnType);
    }


    private boolean hasPathParam(String url, String paramKey) {
        return url.contains("${" + paramKey + "}");
    }


    private void findHttpAnnotation(Annotation[] ans, List<HttpBody> httpBodyList, List<HttpParam> httpParamList, List<HttpHeader> httpHeaderList, List<HttpContext> httpContextList) {
        for (Annotation an : ans) {
            if (HttpParam.class.equals(an.annotationType())) {
                httpParamList.add((HttpParam) an);
            } else if (HttpHeader.class.equals(an.annotationType())) {
                httpHeaderList.add((HttpHeader) an);
            } else if (HttpBody.class.equals(an.annotationType())) {
                httpBodyList.add((HttpBody) an);
            } else if (HttpContext.class.equals(an.annotationType())) {
                httpContextList.add((HttpContext) an);
            }
        }
    }
}

